--- 模块功能：DTU主逻辑
-- @author openLuat
-- @module create
-- @license MIT
-- @copyright openLuat
-- @release 2018.12.27
require "pm"
require "iic"
require "sms"
require "pins"
require "misc"
require "mqtt"
require "utils"
require "lbsLoc"
require "socket"
require "update"
require "httpv2"
require "gpsv3"
require "common"
require "tracker"
module(..., package.seeall)
---------------------------------------------------------- 这个模块为2G和4G通用部分 ----------------------------------------------------------
-- 用户可用API
-- local userapi = default["userapi"]
local datalink = false
local slen = string.len
-- 定时采集任务的参数
local interval, samptime = {0, 0, 0}, {0, 0, 0}
if not _G.is8955 then interval[uart.USB], samptime[uart.USB] = 0, 0 end
-- 获取经纬度
local lat, lng = 0, 0
-- 无网络重启时间，飞行模式启动时间
local rstTim, flyTim = 600000, 300000

-- 保存获取的基站坐标
function setLocation(la, ln)
    lat, lng = la, ln
    log.info("基站定位请求的结果:", lat, lng)
end
function getDatalink() return datalink end
function getTimParam() return interval, samptime end
---------------------------------------------------------- 自动任务列表  ----------------------------------------------------------
-- 用户读取ADC
function getADC(id)
    adc.open(id)
    local adcValue, voltValue = adc.read(id)
    adc.close(id)
    if adcValue ~= 0xFFFF or voltValue ~= 0xFFFF then return (voltValue - voltValue % 3) / 3 end
end
-- 获取纬度
function getLat() return lat end
-- 获取经度
function getLng() return lng end
-- 获取实时经纬度
function getRealLocation()
    lbsLoc.request(function(result, lat, lng, addr) if result then setLocation(lat, lng) end end)
    return lat, lng
end
---------------------------------------------------------- DTU的网络任务部分 ----------------------------------------------------------
local function conver(str)
    if str:match("function(.+)end") then return loadstring(str:match("function(.+)end"))() end
    local hex = str:sub(1, 2):lower() == "0x"
    str = hex and str:sub(3, -1) or str
    local tmp = str:split(" ")
    for v = 1, #tmp do
        if tmp[v]:lower() == "imei" then tmp[v] = hex and (misc.getImei():toHex()) or misc.getImei() end
        if tmp[v]:lower() == "muid" then tmp[v] = hex and (misc.getMuid():toHex()) or misc.getMuid() end
        if tmp[v]:lower() == "imsi" then tmp[v] = hex and (sim.getImsi():toHex()) or sim.getImsi() end
        if tmp[v]:lower() == "iccid" then tmp[v] = hex and (sim.getIccid():toHex()) or sim.getIccid() end
        if tmp[v]:lower() == "csq" then
            tmp[v] = hex and string.format("%02X", net.getRssi()) or tostring(net.getRssi())
        end
    end
    return hex and (table.concat(tmp):fromHex()) or table.concat(tmp)
end
-- 登陆报文
local function loginMsg(str)
    if tonumber(str) == 0 then
        return nil
    elseif tonumber(str) == 1 then
        return json.encode({csq = net.getRssi(), imei = misc.getImei(), iccid = sim.getIccid(), ver = _G.VERSION})
    elseif tonumber(str) == 2 then
        return tostring(net.getRssi()):fromHex() .. (misc.getImei() .. "0"):fromHex() .. sim.getIccid():fromHex()
    elseif type(str) == "string" and #str ~= 0 then
        return conver(str)
    else
        return nil
    end
end

---------------------------------------------------------- SOKCET 服务 ----------------------------------------------------------
local function tcpTask(cid, pios, reg, convert, passon, upprot, dwprot, prot, ping, timeout, addr, port, uid, gap,
                       report, intervalTime, ssl, login)
    cid, prot, timeout, uid = tonumber(cid) or 1, prot:upper(), tonumber(timeout) or 120, tonumber(uid) or 1
    intervalTime = tonumber(intervalTime)
    if not ping or ping == "" then ping = "0x00" end
    if intervalTime and intervalTime > 0 then
        sys.timerLoopStart(sys.publish, intervalTime * 1000, "AUTO_SAMPL_" .. uid)
    end
    local dwprotFnc = dwprot and dwprot[cid] and dwprot[cid] ~= "" and loadstring(dwprot[cid]:match("function(.+)end"))
    local upprotFnc = upprot and upprot[cid] and upprot[cid] ~= "" and loadstring(upprot[cid]:match("function(.+)end"))
    while true do
        local idx = 0
        if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then
            sys.restart("网络初始化失败!")
        end
        local c = prot == "TCP" and socket.tcp(ssl and ssl:lower() == "ssl") or socket.udp()
        if c:connect(addr, port) then
            -- 登陆报文
            if login or loginMsg(reg) then c:send(login or loginMsg(reg)) end
            interval[uid], samptime[uid] = tonumber(gap) or 0, tonumber(report) or 0
            while true do
                datalink = true
                local result, data, param = c:recv(timeout * 1000, "NET_SENT_RDY_" .. (passon and cid or uid))
                if result then
                    -- 这里执行用户自定义的指令
                    if data:sub(1, 5) == "rrpc," or data:sub(1, 7) == "config," then
                        local res, msg = pcall(userapi, data, pios)
                        if not res then log.error("远程查询的API错误:", msg) end
                        if convert == 0 and upprotFnc then -- 转换为用户自定义报文
                            res, msg = pcall(upprotFnc, msg)
                            if not res then log.error("数据流模版错误:", msg) end
                        end
                        if not c:send(msg) then break end
                    elseif convert == 1 then -- 转换HEX String
                        sys.publish("NET_RECV_WAIT_" .. uid, uid, (data:fromHex()))
                    elseif convert == 0 and dwprotFnc then -- 转换用户自定义报文
                        local res, msg = pcall(dwprotFnc, data)
                        if not res or not msg then
                            log.error("数据流模版错误:", msg)
                        else
                            sys.publish("NET_RECV_WAIT_" .. uid, uid, res and msg or data)
                        end
                    else -- 默认不转换
                        sys.publish("NET_RECV_WAIT_" .. uid, uid, data)
                    end
                elseif data == ("NET_SENT_RDY_" .. (passon and cid or uid)) then
                    if convert == 1 then -- 转换为Hex String 报文
                        if not c:send((param:toHex())) then
                            if passon then
                                sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                            end
                            break
                        end
                    elseif convert == 0 and upprotFnc then -- 转换为用户自定义报文
                        local res, msg = pcall(upprotFnc, param)

                        if not res or not msg then
                            log.error("数据流模版错误:", msg)
                        else
                            if not c:send(res and msg or param) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end
                        end
                    else -- 默认不转换
                        if not c:send(param) then
                            if passon then
                                sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                            end
                            break
                        end
                    end
                    if passon then sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_OK\r\n") end
                elseif data == "timeout" then
                    if not c:send(conver(ping)) then break end
                else
                    break
                end
            end
        end
        c:close()
        datalink = false
        sys.wait((2 ^ idx) * 1000)
        idx = idx > 9 and 0 or idx + 1
    end
end
---------------------------------------------------------- MQTT 服务 ----------------------------------------------------------
local function listTopic(str, addImei, ProductKey, deviceName)
    local topics = str:split(";")

    if #topics == 1 and (not addImei or addImei == "") then
        topics[1] = topics[1]:sub(-1, -1) == "/" and topics[1] .. misc.getImei() or topics[1] .. "/" .. misc.getImei()
    else
        local tmp = {}
        for i = 1, #topics, 2 do
            tmp = topics[i]:split("/")
            for v = 1, #tmp do
                if tmp[v]:lower() == "imei" then tmp[v] = misc.getImei() end
                if tmp[v]:lower() == "muid" then tmp[v] = misc.getMuid() end
                if tmp[v]:lower() == "imsi" then tmp[v] = sim.getImsi() end
                if tmp[v]:lower() == "iccid" then tmp[v] = sim.getIccid() end
                if tmp[v]:lower() == "productid" or tmp[v]:lower() == "${productid}" then
                    tmp[v] = ProductKey
                end
                if tmp[v]:lower() == "messageid" or tmp[v]:lower() == "${messageid}" then tmp[v] = "+" end
                if tmp[v]:lower() == "productkey" or tmp[v]:lower() == "${productkey}" or tmp[v]:lower() ==
                    "${yourproductkey}" then tmp[v] = ProductKey end
                if tmp[v]:lower() == "devicename" or tmp[v]:lower() == "${devicename}" or tmp[v]:lower() ==
                    "${yourdevicename}" then tmp[v] = deviceName end
            end
            topics[i] = table.concat(tmp, "/")
            log.info("订阅或发布主题:", i, topics[i])
        end
    end
    return topics
end

local function mqttTask(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, addr, port, usr, pwd,
                        cleansession, sub, pub, qos, retain, uid, clientID, addImei, ssl, will, cert)
    cid, keepAlive, timeout, uid = tonumber(cid) or 1, tonumber(keepAlive) or 300, tonumber(timeout), tonumber(uid)
    cleansession, qos, retain = tonumber(cleansession) or 0, tonumber(qos) or 0, tonumber(retain) or 0
    clientID = (clientID == "" or not clientID) and misc.getImei() or clientID
    if timeout and timeout > 10 then sys.timerLoopStart(sys.publish, timeout * 1000, "AUTO_SAMPL_" .. uid) end
    log.info("mqtt task", cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, addr, port, usr, pwd,
             cleansession, sub, pub, qos, retain, uid, clientID, addImei, ssl, will, cert)
    if type(sub) == "string" then
        sub = listTopic(sub, addImei)
        local topics = {}
        for i = 1, #sub do topics[sub[i]] = tonumber(sub[i + 1]) or qos end
        sub = topics
    end
    if type(pub) == "string" then pub = listTopic(pub, addImei) end
    local dwprotFnc = dwprot and dwprot[cid] and dwprot[cid] ~= "" and loadstring(dwprot[cid]:match("function(.+)end"))
    local upprotFnc = upprot and upprot[cid] and upprot[cid] ~= "" and loadstring(upprot[cid]:match("function(.+)end"))
    if not will or will == "" then
        will = nil
    else
        will = {qos = 1, retain = 0, topic = will, payload = misc.getImei()}
    end
    while true do
        local messageId, idx = false, 1
        if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then
            sys.restart("网络初始化失败!")
        end
        local mqttc = mqtt.client(clientID, keepAlive, conver(usr), conver(pwd), cleansession, will, "3.1.1")
        if mqttc:connect(addr, port, ssl == "tcp_ssl" and ssl or nil, cert) then
            -- 初始化订阅主题
            if mqttc:subscribe(sub, qos) then
                if loginMsg(reg) then
                    mqttc:publish(pub[1], loginMsg(reg), tonumber(pub[2]) or qos, retain)
                end
                while true do
                    datalink = true
                    local r, packet, param = mqttc:receive((timeout or 180) * 1000,
                                                           "NET_SENT_RDY_" .. (passon and cid or uid))
                    if r then
                        log.info("订阅的消息:", packet and packet.topic)
                        messageId = packet.topic:match(".+/rrpc/request/(%d+)")
                        -- 这里执行用户自定义的指令
                        if packet.payload:sub(1, 5) == "rrpc," or packet.payload:sub(1, 7) == "config," then
                            local res, msg = pcall(userapi, packet.payload, pios)
                            if not res then log.error("远程查询的API错误:", msg) end
                            if convert == 0 and upprotFnc then -- 转换为用户自定义报文
                                res, msg = pcall(upprotFnc, msg)
                                if not res then log.error("数据流模版错误:", msg) end
                            end
                            if not mqttc:publish(pub[1], msg, tonumber(pub[2]) or qos, retain) then
                                break
                            end
                        elseif convert == 1 then -- 转换为HEX String
                            sys.publish("UART_SENT_RDY_" .. uid, uid, (packet.payload:fromHex()))
                        elseif convert == 0 and dwprotFnc then -- 转换用户自定义报文
                            local res, msg = pcall(dwprotFnc, packet.payload, packet.topic)
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                sys.publish("UART_SENT_RDY_" .. uid, uid, res and msg or packet.payload)
                            end
                        else -- 默认不转换
                            sys.publish("UART_SENT_RDY_" .. uid, uid, packet.payload)
                        end
                    elseif packet == 'timeout' then
                        -- sys.publish("AUTO_SAMPL_" .. uid)
                        log.debug('The client timeout actively reports status information.')
                    elseif packet == ("NET_SENT_RDY_" .. (passon and cid or uid)) then
                        if convert == 1 then -- 转换为Hex String 报文
                            if not mqttc:publish(pub[1], (param:toHex()), tonumber(pub[2]) or qos, retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end
                        elseif convert == 0 and upprotFnc then -- 转换为用户自定义报文
                            local res, msg, index = pcall(upprotFnc, param)
                            -- log.info("upprotFnc", upprotFnc, res, msg, param:toHex())
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                index = tonumber(index) or 1
                                local pub_topic = (pub[index]:sub(-1, -1) == "+" and messageId) and
                                                      pub[index]:sub(1, -2) .. messageId or pub[index]
                                log.info("-----发布的主题:", pub_topic)
                                if not mqttc:publish(pub_topic, res and msg or param, tonumber(pub[index + 1]) or qos,
                                                     retain) then
                                    if passon then
                                        sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                    end
                                    break
                                end
                            end
                        else
                            local pub_topic = (pub[1]:sub(-1, -1) == "+" and messageId) and pub[1]:sub(1, -2) ..
                                                  messageId or pub[1]
                            log.info("-----发布的主题:", pub_topic)
                            if not mqttc:publish(pub_topic, param, tonumber(pub[2]) or qos, retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end
                        end
                        messageId = false
                        if passon then sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_OK\r\n") end
                        log.info('The client actively reports status information.')
                    else
                        log.warn('The MQTTServer connection is broken.')
                        break
                    end
                end
            end
        end
        datalink = false
        mqttc:disconnect()
        sys.wait((10 * idx) * 1000)
        idx = idx > 9 and 1 or idx + 1
    end
end

local function mqttTaskAliyun(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, addr, port, usr, pwd,
                              cleansession, sub, pub, qos, retain, uid, clientID, addImei, ssl, will, cert)
    cid, keepAlive, timeout, uid = tonumber(cid) or 1, tonumber(keepAlive) or 300, tonumber(timeout), tonumber(uid)
    cleansession, qos, retain = tonumber(cleansession) or 0, tonumber(qos) or 0, tonumber(retain) or 0
    clientID = (clientID == "" or not clientID) and misc.getImei() or clientID
    if timeout and timeout > 0 then sys.timerLoopStart(sys.publish, timeout * 1000, "AUTO_SAMPL_" .. uid) end
    if type(sub) == "string" then
        sub = listTopic(sub, addImei)
        local topics = {}
        for i = 1, #sub do topics[sub[i]] = tonumber(sub[i + 1]) or qos end
        sub = topics
    end
    if type(pub) == "string" then pub = listTopic(pub, addImei) end
    local dwprotFnc = dwprot and dwprot[cid] and dwprot[cid] ~= "" and loadstring(dwprot[cid]:match("function(.+)end"))
    local upprotFnc = upprot and upprot[cid] and upprot[cid] ~= "" and loadstring(upprot[cid]:match("function(.+)end"))
    if not will or will == "" then
        will = nil
    else
        will = {qos = 1, retain = 0, topic = will, payload = misc.getImei()}
    end
    while true do
        local messageId, idx, recvTopic = false, 0, ""
        local contexid, isSubService, isAsyService = 0, false, nil -- isAsyService 是否是异步服务
        -- 判断是否订阅了service，没有订阅的时候优化匹配速度
        for key, value in pairs(sub) do
            if key:match(".+/service/(.+)$") then
                isSubService = true
                break
            end
        end
        -- log.info("aliyun isSubService", isSubService)
        if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then
            sys.restart("网络初始化失败!")
        end
        -- log.info("aliyun client", cleansession)
        local mqttc = mqtt.client(clientID, keepAlive, conver(usr), conver(pwd), cleansession, will, "3.1.1")
        if mqttc:connect(addr, port, ssl == "tcp_ssl" and ssl or nil, cert) then
            -- 初始化订阅主题
            if mqttc:subscribe(sub, qos) then
                if loginMsg(reg) then
                    mqttc:publish(pub[1], loginMsg(reg), tonumber(pub[2]) or qos, retain)
                end
                while true do
                    datalink = true
                    local r, packet, param = mqttc:receive((timeout or 180) * 1000,
                                                           "NET_SENT_RDY_" .. (passon and cid or uid))
                    if r then

                        log.info("订阅的消息:", packet and packet.topic)

                        -- 同步服务调用
                        messageId = packet.topic:match(".+/rrpc/request/(%d+)")
                        if messageId then
                            isAsyService = false
                            recvTopic = packet.topic
                            -- 取出id
                            contexid = packet.payload:match(".+\"id\"[:%s]+\"(%d+)\"")
                            log.info("aliyun isAsyService", isAsyService)
                        elseif isSubService then
                            -- 异步服务调用                           
                            messageId = packet.topic:match(".+/service/(.+)$")
                            if messageId then
                                isAsyService = true
                                recvTopic = packet.topic
                                -- 取出id
                                contexid = packet.payload:match(".+\"id\"[:%s]+\"(%d+)\"")
                                log.info("aliyun isAsyService", isAsyService)
                            end
                        end

                        -- 这里执行用户自定义的指令
                        if packet.payload:sub(1, 5) == "rrpc," or packet.payload:sub(1, 7) == "config," then
                            local res, msg = pcall(userapi, packet.payload, pios)
                            if not res then log.error("远程查询的API错误:", msg) end
                            if convert == 0 and upprotFnc then -- 转换为用户自定义报文
                                res, msg = pcall(upprotFnc, msg)
                                if not res then log.error("数据流模版错误:", msg) end
                            end
                            if not mqttc:publish(pub[1], msg, tonumber(pub[2]) or qos, retain) then
                                break
                            end
                        elseif convert == 1 then -- 转换为HEX String
                            sys.publish("UART_SENT_RDY_" .. uid, uid, (packet.payload:fromHex()))
                        elseif convert == 0 and dwprotFnc then -- 转换用户自定义报文
                            local res, msg = pcall(dwprotFnc, packet.payload, packet.topic)
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                sys.publish("UART_SENT_RDY_" .. uid, uid, res and msg or packet.payload)
                            end
                        else -- 默认不转换
                            sys.publish("UART_SENT_RDY_" .. uid, uid, packet.payload)
                        end
                    elseif packet == 'timeout' then
                        -- sys.publish("AUTO_SAMPL_" .. uid)
                        log.debug('The client timeout actively reports status information.')
                    elseif packet == ("NET_SENT_RDY_" .. (passon and cid or uid)) then
                        if convert == 1 then -- 转换为Hex String 报文
                            if not mqttc:publish(pub[1], (param:toHex()), tonumber(pub[2]) or qos, retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end
                        elseif convert == 0 and upprotFnc then -- 转换为用户自定义报文
                            local res, msg, index, topic = pcall(upprotFnc, param) -- 返回参数，数据，配置pub列表的idx，直接返回topic
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                index = tonumber(index) or 1
                                -- local pub_topic = (pub[index]:sub(-1, -1) == "+" and messageId) and pub[index]:sub(1, -2) .. messageId or pub[index]
                                local pub_topic = ""
                                -- 检查是属性上报还是服务上报
                                if not topic then
                                    if (messageId or (contexid and param:match(".+\"id\"[:%s]+\"(%d+)\"") == contexid)) then
                                        -- 同步还是异步
                                        if isAsyService then
                                            pub_topic = recvTopic .. "_reply"
                                        else
                                            pub_topic = string.gsub(recvTopic, "request", "response")
                                        end
                                    else
                                        pub_topic = pub[index]
                                    end
                                else
                                    -- 直接使用返回的topic
                                    pub_topic = topic
                                end
                                log.info("-----发布的主题:", pub_topic)
                                if not mqttc:publish(pub_topic, res and msg or param, tonumber(pub[index + 1]) or qos,
                                                     retain) then
                                    if passon then
                                        sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                    end
                                    break
                                end
                            end
                        else
                            local pub_topic = ""
                            -- 检查是属性上报还是服务上报
                            if (messageId or (contexid and param:match(".+\"id\"[:%s]+\"(%d+)\"") == contexid)) then
                                -- 同步还是异步
                                if isAsyService then
                                    pub_topic = recvTopic .. "_reply"
                                else
                                    pub_topic = string.gsub(recvTopic, "request", "response")
                                end
                            else
                                pub_topic = pub[1]
                            end
                            log.info("-----发布的主题:", pub_topic)
                            if not mqttc:publish(pub_topic, param, tonumber(pub[2]) or qos, retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end
                        end
                        messageId = false
                        if passon then sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_OK\r\n") end
                        log.info('The client actively reports status information.')
                    else
                        log.warn('The MQTTServer connection is broken.')
                        break
                    end
                end
            end
        end
        datalink = false
        mqttc:disconnect()
        sys.wait((2 ^ idx) * 1000)
        idx = idx > 9 and 0 or idx + 1
    end
end
---------------------------------------------------------- OneNet 云服务器 ----------------------------------------------------------
-- OneNET注册设备，仅限于EDP|MQTT|HTTP|DTU
-- regcode:生产环境注册码
-- uid : 串口id
local function regOneNet(regcode, uid)
    for i = 1, 3 do
        local code, head, body = httpv2.request("POST", "http://api.heclouds.com/register_de", 10000,
                                                {register_code = regcode},
                                                {title = tostring(os.time()), mac = misc.getImei() .. uid}, 2, nil)
        if tonumber(code) == 200 and body then
            log.info("OneNET返回的设备ID:", body)
            local dat, res, err = json.decode(body)
            if res and tonumber(dat.errno) == 0 then return dat end
        end
        sys.wait(5000)
    end
end

-- 注册mqtts
local function regOneNetMqtts(auth, uid)
    local header = {Authorization = auth}
    local name = misc.getImei() .. uid
    log.info("regOneNetMqtts", uid)
    for i = 1, 3 do
        local code, head, body = httpv2.request("POST", "http://api.heclouds.com/mqtt/v1/devices/reg", 10000, nil,
                                                {name = name, desc = name, key = crypto.md5(name, #name)}, 2, nil,
                                                header)
        if tonumber(code) == 200 and body then
            local dat, res, err = json.decode(body)
            if res and tonumber(dat.code_no) == 0 then
                log.info("OneNET返回的MQTTS设备:", dat.data.name)
                return dat.data
            else
                log.error("OneNET MQTTS注册失败:", body)
            end
        end
        sys.wait(5000)
    end
end

-- 注册 iotstudio
local function regOneNetStudio(pid, auth, uid)
    local header = {Authorization = auth}
    local name = misc.getImei() .. uid
    log.info("regOneNetStudio", uid)
    for i = 1, 3 do
        local code, head, body = httpv2.request("GET",
                                                "http://openapi.heclouds.com/common?action=QueryDeviceDetail&version=1&product_id=" ..
                                                    pid .. "&device_name=" .. misc.getImei() .. uid, 10000, nil, nil, 2,
                                                nil, header)
        -- log.info("OneNET Studio 返回:", body)
        if tonumber(code) == 200 and body then
            local dat, res, err = json.decode(body)
            if res and dat.success then
                log.info("OneNET Studio 返回设备:", dat.data.device_name)
                return {pid = dat.data.product_id, key = dat.data.sec_key, name = dat.data.device_name}
            else
                code, head, body = httpv2.request("POST",
                                                  "http://openapi.heclouds.com/common?action=CreateDevice&version=1",
                                                  10000, nil, {product_id = pid, device_name = misc.getImei() .. uid},
                                                  2, nil, header)
                -- log.info("OneNET Studio 返回:", body)
                local dat, res, err = json.decode(body)
                if res and dat.success then
                    log.info("OneNET Studio 注册设备成功:", dat.data.name)
                    return {pid = pid, key = dat.data.sec_key, name = dat.data.name}
                else
                    log.error("OneNET Studio 注册设备失败:", misc.getImei() .. uid)
                    return nil
                end
            end
        end
        sys.wait(5000)
    end
end

-- OneNET注册设备,Modbus
-- key: Master-APIkey
-- uid: 串口id
local function regModbus(key, uid)
    local headers = {
        ['User-Agent'] = 'Mozilla/4.0',
        ['Accept'] = '*/*',
        ['Accept-Language'] = 'zh-CN,zh,cn',
        ['Content-Type'] = 'application/x-www-form-urlencoded',
        ['Content-Length'] = '0',
        ['Connection'] = 'Keep-alive',
        ["Keep-Alive"] = 'timeout=20'
    }
    headers["api-key"] = key
    for i = 1, 3 do
        -- 查询是否已经注册过本设备
        local code, head, body = httpv2.request("GET", "http://api.heclouds.com/devices", 10000,
                                                {key_words = misc.getImei() .. uid}, nil, 2, nil, headers)
        if tonumber(code) == 200 and body then
            log.info("OneNET查询是否注册设备!")
            local dat, result = json.decode(body)
            if result and dat["errno"] == 0 then
                if dat["data"] and dat["data"]["total_count"] > 0 then
                    -- 设备已经注册过 
                    log.info("OneNET已经注册设备:", misc.getImei() .. uid)
                    -- 获取连接地址
                    code, head, body = httpv2.request("GET", "http://api.heclouds.com/s?t=5", 10000, nil, 1, nil,
                                                      headers)
                    if tonumber(code) == 200 and body then
                        log.info("OneNET返回的MODBUS设备的IP和地址:", body)
                        return body:split(":")
                    end

                else
                    log.info("OneNET需要注册设备:", misc.getImei() .. uid)
                    local code, head, body = httpv2.request("POST", "http://api.heclouds.com/devices", 10000, nil, {
                        title = misc.getImei() .. uid,
                        auth_info = {[sim.getIccid():sub(-10, -1) .. uid] = misc.getImei():sub(-7, -1) .. uid}
                    }, 2, nil, headers)
                    if tonumber(code) == 200 and body then
                        log.info("OneNET返回的设备ID:", body)
                        -- 获取连接地址
                        code, head, body = httpv2.request("GET", "http://api.heclouds.com/s?t=5", 10000, nil, 1, nil,
                                                          headers)
                        if tonumber(code) == 200 and body then
                            log.info("OneNET返回的MODBUS设备的IP和地址:", body)
                            return body:split(":")
                        end
                    end
                end
            end
        end
        sys.wait(5000)
    end
end

-- onenet DTU 协议支持
-- cid,通道ID
-- prot::"EDP | MQTT | HTTP | DTU",  //选择一种接入协议（可选，默认为HTTP,DTU对应TCP透传协议）
-- regcode: 设备注册码(正式生产环境注册码)
-- ParserName：脚本名称(上传脚本的时候指定的名称)
local function oneNet_DTU(cid, pios, reg, convert, passon, upprot, dwprot, ping, timeout, addr, port, regcode, pid,
                          ParserName, uid, intervalTime, ssl)
    -- 获取注册的设备ID
    local dat = regOneNet(regcode, uid)
    local login = "*" .. pid .. "#" .. misc.getImei() .. uid .. "#" .. ParserName .. "*"
    tcpTask(cid, pios, reg, convert, passon, upprot, dwprot, "TCP", ping, timeout, addr, port, uid, nil, nil,
            intervalTime, ssl, login)
end

-- onenet modbus 协议支持
local function oneNet_modbus(cid, pios, reg, convert, passon, upprot, dwprot, timeout, key, pid, uid, intervalTime, ssl)
    local host = regModbus(key, uid)
    local phone = (sim.getIccid():sub(-10, -1) .. uid .. "\0"):toHex()
    local pwd = (misc.getImei():sub(-7, -1) .. uid .. "\0"):toHex()
    local pid = (pid .. string.rep("\0", 11 - #pid)):toHex()
    local login = "74797065000000000000006e616d650000000000" .. phone .. pwd .. pid
    tcpTask(cid, pios, reg, convert, passon, upprot, dwprot, "TCP", "\0\0", timeout, host[1], host[2], uid, nil, nil,
            intervalTime, ssl, login:fromHex())
end

-- 新版onenet token
local function onenetToekn(pid, name)
    local StringForSignature =
        os.time() .. "\n" .. "md5" .. "\n" .. "products/384216/devices/867435053902185" .. "\n" .. "2018-10-31"
end

local function onenetToekn(key, pid, name, expirationTime, version)
    local res = "products/" .. pid .. "/devices/" .. name
    local originstr = encryptText
    originstr = expirationTime .. "\n" .. "md5" .. "\n" .. res .. "\n" .. version
    key = crypto.base64_decode(key, slen(key))
    local encry = crypto.hmac_md5(originstr, slen(originstr), key, slen(key)):fromHex()
    local encodestr = string.urlEncode(crypto.base64_encode(encry, slen(encry)))
    local token =
        "version=" .. version .. "&res=" .. string.urlEncode(res) .. "&et=" .. expirationTime .. "&method=md5" ..
            "&sign=" .. encodestr
    return (token)
end

-- onenet mqtts 协议支持
local function oneNet_mqtts(dat, auth, cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, addr, port,
                            key, token, pid, cleansession, qos, retain, uid)
    cid, keepAlive, timeout, uid = tonumber(cid) or 1, tonumber(keepAlive) or 300, tonumber(timeout), tonumber(uid)
    cleansession, qos, retain = tonumber(cleansession) or 0, tonumber(qos) or 0, tonumber(retain) or 0
    if timeout and timeout > 0 then sys.timerLoopStart(sys.publish, timeout * 1000, "AUTO_SAMPL_" .. uid) end

    local dwprotFnc = dwprot and dwprot[cid] and dwprot[cid] ~= "" and loadstring(dwprot[cid]:match("function(.+)end"))
    local upprotFnc = upprot and upprot[cid] and upprot[cid] ~= "" and loadstring(upprot[cid]:match("function(.+)end"))
    while true do
        local idx, rsp = 0, false
        if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then
            sys.restart("网络初始化失败!")
        end

        log.info("onenet mqtts token", addr, port, dat.name, dat.pid, auth)
        local mqttc = mqtt.client(dat.name, keepAlive, tostring(dat.pid), auth, cleanSession)
        local topicPre = "$sys/" .. dat.pid .. "/" .. dat.name
        if mqttc:connect(addr, port) then
            if mqttc:subscribe(topicPre .. "/#", qos) then
                while true do
                    datalink = true
                    local r, packet, param = mqttc:receive((timeout or 180) * 1000,
                                                           "NET_SENT_RDY_" .. (passon and cid or uid))
                    -- log.info("MQTT 订阅的消息:", convert, packet.payload)
                    if r then
                        rsp = packet.topic:match(topicPre .. "/cmd/request/(.+)$")
                        log.info("packet.topic:match", packet.topic, rsp)
                        -- 主题类型-rrpc请求
                        if packet.payload:sub(1, 5) == "rrpc," or packet.payload:sub(1, 7) == "config," then
                            local res, msg = pcall(userapi, packet.payload, pios)
                            if not res then log.error("远程查询的API错误:", msg) end
                            if convert == 0 and upprotFnc then -- 转换为用户自定义报文
                                res, msg = pcall(upprotFnc, msg)
                                if not res then log.error("数据流模版错误:", msg) end
                            end
                            -- if msg then msg = pack.pack("b>HA", ptype, #msg, msg) end
                            if not mqttc:publish(rsp and topicPre .. "/cmd/response/" .. rsp or topicPre ..
                                                     "/dp/post/json", msg, qos, retain) then
                                break
                            end
                            rsp = false
                        else
                            if convert == 1 then -- 转换为HEX String
                                sys.publish("UART_SENT_RDY_" .. uid, uid, (packet.payload:fromHex()))
                            elseif convert == 0 and dwprotFnc then -- 转换用户自定义报文
                                local res, msg = pcall(dwprotFnc, packet.payload)
                                if not res or not msg then
                                    log.error("数据流模版错误:", msg)
                                else
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, res and msg or packet.payload)
                                end
                            else
                                sys.publish("UART_SENT_RDY_" .. uid, uid, packet.payload)
                            end
                        end
                    elseif packet == 'timeout' then
                        -- sys.publish("AUTO_SAMPL_" .. uid)
                        log.debug('The client timeout actively reports status information.')
                    elseif packet == ("NET_SENT_RDY_" .. (passon and cid or uid)) then
                        if convert == 1 then -- 转换为Hex String 报文
                            -- if param then param = pack.pack("b>HA", ptype, #param, param) end
                            if not mqttc:publish(rsp and topicPre .. "/cmd/response/" .. rsp or topicPre ..
                                                     "/dp/post/json", (param:toHex()), qos, retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end

                        elseif convert == 0 and upprotFnc then -- 转换为用户自定义报文
                            local res, msg = pcall(upprotFnc, param)
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                -- if msg then msg = pack.pack("b>HA", ptype, #msg, msg) end
                                if not mqttc:publish(rsp and topicPre .. "/cmd/response/" .. rsp or topicPre ..
                                                         "/dp/post/json", msg, qos, retain) then
                                    if passon then
                                        sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                    end
                                    break
                                end
                            end
                        else -- 不转换报文
                            -- if param then param = pack.pack("b>HA", ptype, #param, param) end
                            if not mqttc:publish(rsp and topicPre .. "/cmd/response/" .. rsp or topicPre ..
                                                     "/dp/post/json", param, qos, retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end
                        end
                        log.info("mqttc:publish",
                                 rsp and topicPre .. "/cmd/response/" .. rsp or topicPre .. "/dp/post/json")
                        rsp = false
                        if passon then sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_OK\r\n") end
                        log.info('The client actively reports status information.')
                    else
                        log.warn('The MQTTServer connection is broken.')
                        break
                    end
                end
            end
        end
        datalink = false
        mqttc:disconnect()
        sys.wait((2 ^ idx) * 1000)
        idx = idx > 9 and 0 or idx + 1
    end
end

-- onenet mqtts 协议支持
local function oneNet_mqttStudio(dat, auth, cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, addr,
                                 port, key, token, pid, cleansession, qos, retain, uid)
    cid, keepAlive, timeout, uid = tonumber(cid) or 1, tonumber(keepAlive) or 300, tonumber(timeout), tonumber(uid)
    cleansession, qos, retain = tonumber(cleansession) or 0, tonumber(qos) or 0, tonumber(retain) or 0
    if timeout and timeout > 0 then sys.timerLoopStart(sys.publish, timeout * 1000, "AUTO_SAMPL_" .. uid) end
    local topicPre = "$sys/" .. dat.pid .. "/" .. dat.name .. "/thing/"
    local subs, pubs = {}, {}
    -- 手动订阅主题
    subs[topicPre .. "property/post/reply"] = qos
    subs[topicPre .. "property/set"] = qos
    subs[topicPre .. "property/get"] = qos
    subs[topicPre .. "service/+/invoke"] = qos
    --
    pubs[1] = topicPre .. "property/post"
    pubs[2] = topicPre .. "property/set_reply"
    pubs[3] = topicPre .. "property/get_reply"
    -- pubs[4] = topicPre.."service/{identifier}/invoke_reply"  
    local dwprotFnc = dwprot and dwprot[cid] and dwprot[cid] ~= "" and loadstring(dwprot[cid]:match("function(.+)end"))
    local upprotFnc = upprot and upprot[cid] and upprot[cid] ~= "" and loadstring(upprot[cid]:match("function(.+)end"))
    while true do
        local idx, messageid, recvTopic = 0, false, ""
        if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then
            sys.restart("网络初始化失败!")
        end

        -- log.info("onenet mqtts token", addr, port, dat.name, dat.pid, auth)
        local mqttc = mqtt.client(dat.name, keepAlive, tostring(dat.pid), auth, cleanSession)

        if mqttc:connect(addr, port) then
            if mqttc:subscribe(subs, qos) then
                while true do
                    datalink = true
                    local r, packet, param = mqttc:receive((timeout or 180) * 1000,
                                                           "NET_SENT_RDY_" .. (passon and cid or uid))
                    if r then
                        messageid = packet.topic:match(".+/service/(.+)/invoke$")
                        log.info("MQTT 订阅的消息:", packet.topic, packet.payload)
                        if messageid then recvTopic = packet.topic end
                        -- 主题类型-rrpc请求
                        -- if packet.payload:sub(1, 5) == "rrpc," or packet.payload:sub(1, 7) == "config," then
                        --     local res, msg = pcall(userapi, packet.payload, pios)
                        --     if not res then log.error("远程查询的API错误:", msg) end
                        --     if convert == 0 and upprotFnc then -- 转换为用户自定义报文
                        --         res, msg = pcall(upprotFnc, msg)
                        --         if not res then log.error("数据流模版错误:", msg) end
                        --     end
                        --     -- if msg then msg = pack.pack("b>HA", ptype, #msg, msg) end
                        --     if not mqttc:publish(messageid and topicPre .. messageid .. "/invoke_reply" or topicPre ..
                        --                              "property/post", msg, qos, retain) then
                        --         break
                        --     end
                        --     messageid = false
                        -- else
                        -- 给payload里面插入topic后缀
                        local start = string.find(packet.payload, "{")
                        if start then
                            packet.payload = table.concat({
                                "{", "\"topic\":\"", packet.topic:match(".+/thing/(.+)$"), "\",",
                                string.sub(packet.payload, start + 1)
                            })
                        end
                        if convert == 1 then -- 转换为HEX String
                            sys.publish("UART_SENT_RDY_" .. uid, uid, (packet.payload:fromHex()))
                        elseif convert == 0 and dwprotFnc then -- 转换用户自定义报文
                            local res, msg = pcall(dwprotFnc, packet.payload, packet.topic)
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                sys.publish("UART_SENT_RDY_" .. uid, uid, res and msg or packet.payload)
                            end
                        else
                            sys.publish("UART_SENT_RDY_" .. uid, uid, packet.payload)
                        end
                        -- end
                    elseif packet == 'timeout' then
                        -- sys.publish("AUTO_SAMPL_" .. uid)
                        log.debug('The client timeout actively reports status information.')
                    elseif packet == ("NET_SENT_RDY_" .. (passon and cid or uid)) then
                        if convert == 1 then -- 转换为Hex String 报文
                            -- if param then param = pack.pack("b>HA", ptype, #param, param) end
                            if not mqttc:publish(messageid and recvTopic .. "_reply" or pubs[1], (param:toHex()), qos,
                                                 retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end

                        elseif convert == 0 and upprotFnc then -- 转换为用户自定义报文
                            local res, msg, topic = pcall(upprotFnc, param)
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                -- if msg then msg = pack.pack("b>HA", ptype, #msg, msg) end
                                if not mqttc:publish(topic and topicPre .. topic or
                                                         (messageid and recvTopic .. "_reply" or pubs[1]), msg, qos,
                                                     retain) then
                                    if passon then
                                        sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                    end
                                    break
                                end
                            end
                        else -- 不转换报文
                            -- if param then param = pack.pack("b>HA", ptype, #param, param) end
                            if not mqttc:publish(messageid and recvTopic .. "_reply" or pubs[1], param, qos, retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end
                        end
                        log.info("mqttc:publish",
                                 messageid and topicPre .. messageid .. "/invoke_reply" or topicPre .. "property/post")
                        messageid = false
                        -- if passon then sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_OK\r\n") end
                        log.info('The client actively reports status information.')
                    else
                        log.warn('The MQTTServer connection is broken.')
                        break
                    end
                end
            end
        end
        datalink = false
        mqttc:disconnect()
        sys.wait((2 ^ idx) * 1000)
        idx = idx > 9 and 0 or idx + 1
    end
end
-- 启动onenet iotstudio mqtts 任务 
local function startOnenetStudio(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, addr, port, key,
                                 token, pid, cleansession, qos, retain, uid)
    -- 如果没写key，者自动注册
    local dat
    if key and slen(key) > 10 then
        dat = {name = misc.getImei() .. uid, pid = pid, key = key}
    else
        dat = regOneNetStudio(pid, token, uid)
    end
    local auth = onenetToekn(dat.key, tostring(dat.pid), dat.name, tostring(os.time() + 3600 * 24 * 30), "2018-10-31")
    -- log.info("onenet token", auth)
    -- log.info("mqtts", dat.name, dat.pid, auth)
    oneNet_mqttStudio(dat, auth, cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, addr, port, key,
                      token, pid, cleansession, qos, retain, uid)
end

-- 启动mqtts 任务
local function startOnenetMqtts(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, addr, port, key,
                                token, pid, cleansession, qos, retain, uid)
    -- 如果没写key，者自动注册
    local dat
    if key and slen(key) > 10 then
        dat = {name = misc.getImei() .. uid, pid = pid, key = key}
    else
        dat = regOneNetMqtts(token, uid)
    end
    local auth = onenetToekn(dat.key, tostring(dat.pid), dat.name, tostring(os.time() + 3600 * 24 * 30), "2018-10-31")
    -- log.info("onenet mqtts", dat.name, dat.pid, auth)
    oneNet_mqtts(dat, auth, cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, addr, port, key, token,
                 pid, cleansession, qos, retain, uid)
end

-- onenet mqtt 协议支持
local function oneNet_mqtt(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, addr, port, regcode,
                           pid, ptype, cleansession, qos, retain, uid)
    cid, keepAlive, timeout, uid = tonumber(cid) or 1, tonumber(keepAlive) or 300, tonumber(timeout), tonumber(uid)
    cleansession, qos, retain, ptype = tonumber(cleansession) or 0, tonumber(qos) or 0, tonumber(retain) or 0,
                                       tonumber(ptype) or 3
    if timeout and timeout > 0 then sys.timerLoopStart(sys.publish, timeout * 1000, "AUTO_SAMPL_" .. uid) end
    local dat = regOneNet(regcode, uid)
    local dwprotFnc = dwprot and dwprot[cid] and dwprot[cid] ~= "" and loadstring(dwprot[cid]:match("function(.+)end"))
    local upprotFnc = upprot and upprot[cid] and upprot[cid] ~= "" and loadstring(upprot[cid]:match("function(.+)end"))
    while true do
        local idx, rsp = 0, false
        if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then
            sys.restart("网络初始化失败!")
        end
        local mqttc = mqtt.client(dat.data.device_id, keepAlive, pid, dat.data.key, cleanSession)
        if mqttc:connect(addr, port) then
            if mqttc:subscribe(subs, qos) then
                while true do
                    datalink = true
                    local r, packet, param = mqttc:receive((timeout or 180) * 1000,
                                                           "NET_SENT_RDY_" .. (passon and cid or uid))
                    if r then
                        messageid = packet.topic:match(".+/service/(.+)/invoke$")
                        log.info("MQTT 订阅的消息:", packet.topic, packet.payload)
                        if messageid then recvTopic = packet.topic end
                        -- 主题类型-rrpc请求
                        -- if packet.payload:sub(1, 5) == "rrpc," or packet.payload:sub(1, 7) == "config," then
                        --     local res, msg = pcall(userapi, packet.payload, pios)
                        --     if not res then log.error("远程查询的API错误:", msg) end
                        --     if convert == 0 and upprotFnc then -- 转换为用户自定义报文
                        --         res, msg = pcall(upprotFnc, msg)
                        --         if not res then log.error("数据流模版错误:", msg) end
                        --     end
                        --     -- if msg then msg = pack.pack("b>HA", ptype, #msg, msg) end
                        --     if not mqttc:publish(messageid and topicPre .. messageid .. "/invoke_reply" or topicPre ..
                        --                              "property/post", msg, qos, retain) then
                        --         break
                        --     end
                        --     messageid = false
                        -- else
                        -- 给payload里面插入topic后缀
                        local start = string.find(packet.payload, "{")
                        if start then
                            packet.payload = table.concat({
                                "{", "\"topic\":\"", packet.topic:match(".+/thing/(.+)$"), "\",",
                                string.sub(packet.payload, start + 1)
                            })
                        end
                        if convert == 1 then -- 转换为HEX String
                            sys.publish("UART_SENT_RDY_" .. uid, uid, (packet.payload:fromHex()))
                        elseif convert == 0 and dwprotFnc then -- 转换用户自定义报文
                            local res, msg = pcall(dwprotFnc, packet.payload, packet.topic)
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                sys.publish("UART_SENT_RDY_" .. uid, uid, res and msg or packet.payload)
                            end
                        else
                            sys.publish("UART_SENT_RDY_" .. uid, uid, packet.payload)
                        end
                        -- end
                    elseif packet == 'timeout' then
                        -- sys.publish("AUTO_SAMPL_" .. uid)
                        log.debug('The client timeout actively reports status information.')
                    elseif packet == ("NET_SENT_RDY_" .. (passon and cid or uid)) then
                        if convert == 1 then -- 转换为Hex String 报文
                            -- if param then param = pack.pack("b>HA", ptype, #param, param) end
                            if not mqttc:publish(messageid and recvTopic .. "_reply" or pubs[1], (param:toHex()), qos,
                                                 retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end

                        elseif convert == 0 and upprotFnc then -- 转换为用户自定义报文
                            local res, msg, topic = pcall(upprotFnc, param)
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                -- if msg then msg = pack.pack("b>HA", ptype, #msg, msg) end
                                if not mqttc:publish(topic and topicPre .. topic or
                                                         (messageid and recvTopic .. "_reply" or pubs[1]), msg, qos,
                                                     retain) then
                                    if passon then
                                        sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                    end
                                    break
                                end
                            end
                        else -- 不转换报文
                            -- if param then param = pack.pack("b>HA", ptype, #param, param) end
                            if not mqttc:publish(messageid and recvTopic .. "_reply" or pubs[1], param, qos, retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end
                        end
                        log.info("mqttc:publish",
                                 messageid and topicPre .. messageid .. "/invoke_reply" or topicPre .. "property/post")
                        messageid = false
                        -- if passon then sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_OK\r\n") end
                        log.info('The client actively reports status information.')
                    else
                        log.warn('The MQTTServer connection is broken.')
                        break
                    end
                end
            end
        end
        datalink = false
        mqttc:disconnect()
        sys.wait((2 ^ idx) * 1000)
        idx = idx > 9 and 0 or idx + 1
    end
end
---------------------------------------------------------- DevelopLink 云服务器 ----------------------------------------------------------
-- DevelopLink注册设备，仅限于MQTT
-- regcode:生产环境注册码
-- uid : 串口id
local function regDevlink(regcode, uid)
    for i = 1, 3 do
        local code, head, body = httpv2.request("POST", "http://open.developlink.cloud/api/v2/register_de", 10000,
                                                {register_code = regcode},
                                                {title = tostring(os.time()), mac = misc.getImei() .. uid}, 2, nil)
        if tonumber(code) == 200 and body then
            log.info("Devlink返回的设备ID:", body)
            local dat, res, err = json.decode(body)
            if res and tonumber(dat.errno) == 0 then return dat end
        end
        sys.wait(5000)
    end

end

local function devlinkToken(id, key)
    local username = id .. "|" .. (os.time() * 1000)
    local password = username .. "|" .. key
    password = crypto.md5(password, slen(password))
    return {user = username, pass = password:lower()}
end

-- devlink mqtt 协议支持
local function devlink_mqtt(dat, cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, addr, port, name, sid,
                            skey, pid, retain, qos, uid)
    cid, keepAlive, uid = tonumber(cid) or 1, tonumber(keepAlive) or 300, tonumber(uid)
    cleansession, qos, retain = 0, tonumber(qos) or 0, tonumber(retain) or 0
    local dwprotFnc = dwprot and dwprot[cid] and dwprot[cid] ~= "" and loadstring(dwprot[cid]:match("function(.+)end"))
    local upprotFnc = upprot and upprot[cid] and upprot[cid] ~= "" and loadstring(upprot[cid]:match("function(.+)end"))
    local topicPre = "/" .. pid .. "/" .. dat.name .. "/"

    while true do
        local idx, rsp = 0, false
        if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then
            sys.restart("网络初始化失败!")
        end
        local auth = devlinkToken(sid, skey)
        log.info("devlink mqtt", dat.name, keepAlive, auth.user, auth.pass)
        local mqttc = mqtt.client(dat.name, keepAlive, auth.user, auth.pass, cleanSession)
        if mqttc:connect(addr, port) then
            if mqttc:subscribe(topicPre .. "#", qos) then
                log.info("devlink_mqtt sub", topicPre .. "#")
                while true do
                    datalink = true
                    local r, packet, param = mqttc:receive(180 * 1000, "NET_SENT_RDY_" .. (passon and cid or uid))
                    log.info("MQTT 订阅的消息:", convert, packet.payload)
                    if r then
                        rsp = packet.topic:match("function/(%w+)")

                        -- 主题类型-rrpc请求
                        if rsp then
                            local body, res = json.decode(packet.payload)

                            if body and res and body["function"] == "rrpc" then
                                local para = body["inputs"]
                                local fun, value
                                if para and #para > 0 then
                                    for i = 1, #para do
                                        if para[i].name == "fun" then
                                            fun = para[i].value
                                        elseif para[i].name == "value" then
                                            value = para[i].value
                                        end
                                    end
                                end

                                local res, msg = pcall(userapi, "rrpc," .. fun .. (value and "," .. value or ""), pios)
                                if not res then
                                    log.error("远程查询的API错误:", msg)
                                end
                                if convert == 0 and upprotFnc then -- 转换为用户自定义报文
                                    res, msg = pcall(upprotFnc, msg)
                                    if not res then
                                        log.error("数据流模版错误:", msg)
                                    end
                                end

                                -- {
                                --     "timestamp":1601196762389, //毫秒时间戳
                                --     "messageId":"与下行消息中的messageId相同",
                                --     "output":"success", //返回执行结果,具体类型与物模型中功能输出类型一致
                                --     "success":true,
                                --     }
                                local retbuff = {
                                    timestamp = os.time() * 1000,
                                    messageId = body["messageId"],
                                    success = true,
                                    output = msg
                                }

                                if not mqttc:publish(rsp and topicPre .. "function/invoke/reply", json.encode(retbuff),
                                                     qos, retain) then break end
                                rsp = false
                            end
                        end

                        if convert == 1 then -- 转换为HEX String
                            sys.publish("UART_SENT_RDY_" .. uid, uid, (packet.payload:fromHex()))
                        elseif convert == 0 and dwprotFnc then -- 转换用户自定义报文
                            local res, msg = pcall(dwprotFnc, packet.payload)
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                sys.publish("UART_SENT_RDY_" .. uid, uid, res and msg or packet.payload)
                            end
                        else
                            sys.publish("UART_SENT_RDY_" .. uid, uid, packet.payload)
                        end

                    elseif packet == 'timeout' then
                        -- sys.publish("AUTO_SAMPL_" .. uid)
                        log.debug('The client timeout actively reports status information.')
                    elseif packet == ("NET_SENT_RDY_" .. (passon and cid or uid)) then
                        if convert == 1 then -- 转换为Hex String 报文
                            if not mqttc:publish(rsp and topicPre .. "function/invoke/reply" or topicPre ..
                                                     "properties/report", (param:toHex()), qos, retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end
                        elseif convert == 0 and upprotFnc then -- 转换为用户自定义报文
                            local res, msg = pcall(upprotFnc, param)
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                if not mqttc:publish(rsp and topicPre .. "function/invoke/reply" or topicPre ..
                                                         "properties/report", msg, qos, retain) then
                                    if passon then
                                        sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                    end
                                    break
                                end
                            end
                        else -- 不转换报文
                            if not mqttc:publish(rsp and topicPre .. "function/invoke/reply" or topicPre ..
                                                     "properties/report", param, qos, retain) then
                                if passon then
                                    sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_ERROR\r\n")
                                end
                                break
                            end
                        end
                        rsp = false
                        if passon then sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_OK\r\n") end
                        log.info('The client actively reports status information.')
                    else
                        log.warn('The MQTTServer connection is broken.')
                        break
                    end
                end
            end
        end
        datalink = false
        mqttc:disconnect()
        sys.wait((2 ^ idx) * 1000)
        idx = idx > 9 and 0 or idx + 1
    end
end

-- 启动mqtts 任务
local function startDevlinkMqtt(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, addr, port, name, id, key,
                                pid, retain, qos, uid)
    -- 如果没写key，者自动注册
    local dat

    dat = {name = name and slen(name) > 2 and name or misc.getImei(), pid = pid, key = key}

    -- dat = regOneNetMqtts(token, uid)

    -- log.info("onenet mqtts", dat.name, dat.pid, auth)
    devlink_mqtt(dat, cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, addr, port, name, id, key, pid,
                 retain, qos, uid)
end

---------------------------------------------------------- 阿里IOT云 ----------------------------------------------------------
local alikey = "/alikey.cnf"
-- 处理表的RFC3986编码
function table.rawurlEncode(t)
    local msg = {}
    for k, v in table.gsort(t) do
        table.insert(msg, string.rawurlEncode(k) .. '=' .. string.rawurlEncode(v))
        table.insert(msg, '&')
    end
    table.remove(msg)
    return table.concat(msg)
end
local function aliCommonParam(Action, RegionId, ProductKey, AccessKeyId, AccessKeySecret)
    AccessKeySecret = AccessKeySecret .. "&"
    local param = {
        Format = "JSON",
        Version = "2018-01-20",
        AccessKeyId = AccessKeyId,
        SignatureMethod = "HMAC-SHA1",
        Timestamp = "",
        SignatureVersion = "1.0",
        SignatureNonce = os.time() .. "",
        RegionId = RegionId,
        Action = Action,
        ProductKey = ProductKey,
        DeviceName = misc.getImei()
    }
    local c = os.date("!*t")
    param.Timestamp = string.format("%04d-%02d-%02dT%02d:%02d:%02dZ", c.year, c.month, c.day, c.hour, c.min, c.sec)
    local StringToSign = "GET&%2F&" .. table.rawurlEncode(param):rawurlEncode()
    local Signature = crypto.hmac_sha1(StringToSign, #StringToSign, AccessKeySecret, #AccessKeySecret)
    param.Signature = crypto.base64_encode(Signature:fromHex())
    return table.rawurlEncode(param)
end
local function registerDevice(RegionId, ProductKey, AccessKeyId, AccessKeySecret)
    if io.exists(alikey) then
        local dat, res, err = json.decode(io.readFile(alikey))
        if res then return dat.Data.DeviceName, dat.Data.DeviceSecret end
    end
    local param = aliCommonParam("RegisterDevice", RegionId, ProductKey, AccessKeyId, AccessKeySecret)
    for i = 1, 3 do
        local code, head, body = httpv2.request("GET", "https://iot." .. RegionId .. ".aliyuncs.com/?" .. param, 10000,
                                                nil, nil, 2)
        if tonumber(code) == 200 and body then
            local dat, result, errinfo = json.decode(body)
            if result then
                if dat.Success and dat.Data then
                    io.writeFile(alikey, body)
                    return dat.Data.DeviceName, dat.Data.DeviceSecret
                elseif dat.Code == "iot.device.AlreadyExistedDeviceName" then
                    break
                end
            end
        end
        log.warn("阿里云注册请求失败:", code, body)
        sys.wait(5000)
    end
    param = aliCommonParam("QueryDeviceDetail", RegionId, ProductKey, AccessKeyId, AccessKeySecret)
    for i = 1, 3 do
        local code, head, body = httpv2.request("GET", "https://iot." .. RegionId .. ".aliyuncs.com/?" .. param, 10000,
                                                nil, nil, 2)
        if tonumber(code) == 200 and body then
            local dat, result, errinfo = json.decode(body)
            if result and dat.Success and dat.Data then
                io.writeFile(alikey, body)
                return dat.Data.DeviceName, dat.Data.DeviceSecret
            end
        end
        sys.wait(5000)
    end
end
local function getOneSecret(RegionId, ProductKey, ProductSecret)
    if io.exists(alikey) then
        local dat, res, err = json.decode(io.readFile(alikey))
        if res then return dat.data.deviceName, dat.data.deviceSecret end
    end
    local random = rtos.tick()
    local data = "deviceName" .. misc.getImei() .. "productKey" .. ProductKey .. "random" .. random
    local sign = crypto.hmac_md5(data, #data, ProductSecret, #ProductSecret)
    local body = "productKey=" .. ProductKey .. "&deviceName=" .. misc.getImei() .. "&random=" .. random .. "&sign=" ..
                     sign .. "&signMethod=HmacMD5"
    for i = 1, 3 do
        local code, head, body = httpv2.request("POST", "https://iot-auth." .. RegionId ..
                                                    ".aliyuncs.com/auth/register/device", 10000, nil, body, 1)
        if tonumber(code) == 200 and body then
            local dat, result, errinfo = json.decode(body)
            if result and dat.message and dat.data then
                io.writeFile(alikey, body)
                return dat.data.deviceName, dat.data.deviceSecret
            end
        end
        log.warn("阿里云查询请求失败:", code, body)
        sys.wait(5000)
    end
end

-- 一机一密方案，所有方案最终都会到这里执行
local function aliyunOmok(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, RegionId, ProductKey,
                          deviceSecret, deviceName, ver, cleansession, qos, uid, sub, pub)
    cid, keepAlive, timeout, uid = tonumber(cid) or 1, tonumber(keepAlive) or 300, tonumber(timeout), tonumber(uid)
    cleansession, qos = tonumber(cleansession) or 0, tonumber(qos) or 0
    local data = "clientId" .. sim.getIccid() .. "deviceName" .. deviceName .. "productKey" .. ProductKey
    local usr = deviceName .. "&" .. ProductKey
    local pwd = crypto.hmac_sha1(data, #data, deviceSecret, #deviceSecret)
    local clientID = sim.getIccid() .. "|securemode=3,signmethod=hmacsha1|"
    local addr = ProductKey .. ".iot-as-mqtt." .. RegionId .. ".aliyuncs.com"
    local port = 1883
    if type(sub) ~= "string" or sub == "" then
        sub = ver:lower() == "basic" and "/" .. ProductKey .. "/" .. deviceName .. "/get" or "/" .. ProductKey .. "/" ..
                  deviceName .. "/user/get"
    else
        sub = listTopic(sub, "addImei", ProductKey, deviceName)
        local topics = {}
        for i = 1, #sub do topics[sub[i]] = tonumber(sub[i + 1]) or qos end
        sub = topics
    end
    if type(pub) ~= "string" or pub == "" then
        pub =
            ver:lower() == "basic" and "/" .. ProductKey .. "/" .. deviceName .. "/update" or "/" .. ProductKey .. "/" ..
                deviceName .. "/user/update"
    else
        pub = listTopic(pub, "addImei", ProductKey, deviceName)
    end
    -- /sys/a12rinVjYRa/${deviceName}/thing/event/property/post
    -- 发布
    -- 设备属性上报
    -- /sys/a12rinVjYRa/${deviceName}/thing/event/property/post_reply
    -- 订阅
    -- 云端响应属性上报
    -- log.info("aliyun sub toppic ", #sub, json.encode(sub))
    local dwprotFnc = dwprot and dwprot[cid] and dwprot[cid] ~= "" and loadstring(dwprot[cid]:match("function(.+)end"))
    local upprotFnc = upprot and upprot[cid] and upprot[cid] ~= "" and loadstring(upprot[cid]:match("function(.+)end"))
    mqttTaskAliyun(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, addr, port, usr, pwd,
                   cleansession, sub, pub, qos, retain, uid, clientID, "addImei", ssl, will)

end
-- API 自动注册方案
local function aliyunAuto(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, RegionId, ProductKey,
                          AccessKeyId, AccessKeySecret, ver, cleansession, qos, uid, sub, pub)
    local deviceName, deviceSecret = registerDevice(RegionId, ProductKey, AccessKeyId, AccessKeySecret, uid)
    if not deviceName or not deviceSecret then
        log.error("阿里云注册失败:", AccessKeyId, AccessKeySecret)
        return
    end
    log.warn("自动注册返回三元组:", deviceName ~= nil, deviceSecret ~= nil)
    aliyunOmok(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, RegionId, ProductKey, deviceSecret,
               deviceName, ver, cleansession, qos, uid, sub, pub)
end
-- 一型一密认证方案
local function aliyunOtok(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, RegionId, ProductKey,
                          ProductSecret, ver, cleansession, qos, uid, sub, pub)
    local deviceName, deviceSecret = getOneSecret(RegionId, ProductKey, ProductSecret)
    if not deviceName or not deviceSecret then
        log.error("阿里云注册失败:", AccessKeyId, AccessKeySecret)
        return
    end
    log.warn("一型一密动态注册返回三元组:", deviceName ~= nil, deviceSecret ~= nil)
    aliyunOmok(cid, pios, reg, convert, passon, upprot, dwprot, keepAlive, timeout, RegionId, ProductKey, deviceSecret,
               deviceName, ver, cleansession, qos, uid, sub, pub)
end

---------------------------------------------------------- 百度天工 ----------------------------------------------------------

---------------------------------------------------------- 腾讯IOT云 ----------------------------------------------------------

---------------------------------------------------------- 参数配置,任务转发，线程守护主进程----------------------------------------------------------
function connect(pios, conf, reg, convert, passon, upprot, dwprot)
    local flyTag = false
    if not socket.isReady() and not sys.waitUntil("IP_READY_IND", rstTim) then sys.restart("网络初始化失败!") end
    sys.waitUntil("DTU_PARAM_READY", 120000)
    while not ntp.isEnd() do sys.wait(1000) end
    -- 自动创建透传任务并填入参数
    for k, v in pairs(conf or {}) do
        log.info("Task parameter information:", k, pios, reg, convert, passon, upprot, dwprot, unpack(v))
        if v[1] and (v[1]:upper() == "TCP" or v[1]:upper() == "UDP") then
            log.warn("----------------------- TCP/UDP is start! --------------------------------------")
            sys.taskInit(tcpTask, k, pios, reg, convert, passon, upprot, dwprot, unpack(v))
        elseif v[1] and v[1]:upper() == "MQTT" then
            log.warn("----------------------- MQTT is start! --------------------------------------")
            sys.taskInit(mqttTask, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 2))
        elseif v[1] and v[1]:upper() == "HTTP" then
            log.warn("----------------------- HTTP is start! --------------------------------------")
            sys.taskInit(function(cid, convert, passon, upprot, dwprot, uid, method, url, timeout, way, dtype, basic,
                                  headers, iscode, ishead, isbody)
                cid, timeout, uid = tonumber(cid) or 1, tonumber(timeout) or 30, tonumber(uid) or 1
                way, dtype = tonumber(way) or 1, tonumber(dtype) or 1
                local dwprotFnc = dwprot and dwprot[cid] and dwprot[cid] ~= "" and
                                      loadstring(dwprot[cid]:match("function(.+)end"))
                local upprotFnc = upprot and upprot[cid] and upprot[cid] ~= "" and
                                      loadstring(upprot[cid]:match("function(.+)end"))
                while true do
                    datalink = socket.isReady()
                    local result, msg = sys.waitUntil("NET_SENT_RDY_" .. (passon and cid or uid))

                    if result and msg then
                        if convert == 1 then -- 转换为Hex String 报文
                            msg = msg:toHex()
                        elseif convert == 0 and upprotFnc then -- 转换为用户自定义报文
                            local res, dat = pcall(upprotFnc, msg)
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            end
                            msg = res and dat or msg
                        end
                        if passon then sys.publish("UART_SENT_RDY_" .. uid, uid, "SEND_OK\r\n") end
                        local code, head, body = httpv2.request(method:upper(), url, timeout * 1000,
                                                                way == 0 and msg or nil, way == 1 and msg or nil, dtype,
                                                                basic, headers)
                        log.info("http", code, head)
                        local headstr = ""
                        if type(head) == "table" then
                            for k, v in pairs(head) do
                                headstr = headstr .. k .. ": " .. v .. "\r\n"
                            end
                        else
                            headstr = head
                        end
                        if convert == 1 then -- 转换HEX String
                            local str = (tonumber(iscode) ~= 1 and code .. "\r\n" or "") ..
                                            (tonumber(ishead) ~= 1 and headstr or "") ..
                                            (tonumber(isbody) ~= 1 and body and (body:fromHex()) or "")
                            sys.publish("NET_RECV_WAIT_" .. uid, uid, str)
                        elseif convert == 0 and dwprotFnc then -- 转换用户自定义报文
                            local res, code, head, body = pcall(dwprotFnc, code, head, body)
                            if not res or not msg then
                                log.error("数据流模版错误:", msg)
                            else
                                local str = (tonumber(iscode) ~= 1 and code .. "\r\n" or "") ..
                                                (tonumber(ishead) ~= 1 and headstr or "") ~= 1 ..
                                                (tonumber(isbody) ~= 1 and body or "")
                                sys.publish("NET_RECV_WAIT_" .. uid, uid, res and str or code)
                            end
                        else -- 默认不转换
                            sys.publish("NET_RECV_WAIT_" .. uid, uid,
                                        (tonumber(iscode) ~= 1 and code .. "\r\n" or "") ..
                                            (tonumber(ishead) ~= 1 and headstr or "") ..
                                            (tonumber(isbody) ~= 1 and body or ""))
                        end
                    end
                    sys.wait(100)
                end
            end, k, convert, passon, upprot, dwprot, unpack(v, 2))
        elseif v[1] and v[1]:upper() == "ONENET" then
            log.warn("----------------------- OneNET is start! --------------------------------------")
            if v[2]:upper() == "DTU" then
                sys.taskInit(oneNet_DTU, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 3))
            elseif v[2]:upper() == "MQTTS" then
                sys.taskInit(startOnenetMqtts, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 3))
            elseif v[2]:upper() == "IOT" then
                sys.taskInit(startOnenetStudio, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 3))
            elseif v[2]:upper() == "MQTT" then
                sys.taskInit(oneNet_mqtt, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 3))
            elseif v[2]:upper() == "HTTP" then
                sys.taskInit(oneNet_http, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 3))
            elseif v[2]:upper() == "EDP" then
                sys.taskInit(oneNet_edp, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 3))
            elseif v[2]:upper() == "MODBUS" then
                sys.taskInit(oneNet_modbus, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 3))
            end
        elseif v[1] and v[1]:upper() == "DEVLINK" then
            log.warn("----------------------- DEVLINK is start! --------------------------------------")
            if v[2]:upper() == "MQTT" then
                sys.taskInit(startDevlinkMqtt, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 3))
            end
        elseif v[1] and v[1]:upper() == "ALIYUN" then
            log.warn("----------------------- Aliyun iot is start! --------------------------------------")
            if v[2]:upper() == "AUTO" then
                sys.taskInit(aliyunAuto, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 3))
            elseif v[2]:upper() == "OTOK" then -- 一型一密
                sys.taskInit(aliyunOtok, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 3))
            elseif v[2]:upper() == "OMOK" then -- 一机一密
                sys.taskInit(aliyunOmok, k, pios, reg, convert, passon, upprot, dwprot, unpack(v, 3))
            end
        end
    end
    -- 守护进程
    -- while true do
    --     -- 这里是网络正常,但是链接服务器失败重启
    --     -- if datalink then sys.timerStart(sys.restart, rstTim, "Server connection failed") end
    --     sys.wait(1000)
    -- end
end
net.switchFly(false)
-- NTP同步失败强制重启
-- local tid = sys.timerStart(function()
--     net.switchFly(true)
--     sys.timerStart(net.switchFly, 5000, false)
-- end, flyTim)
sys.subscribe("IP_READY_IND", function()
    -- sys.timerStop(tid)
    log.info("---------------------- 网络注册已成功 ----------------------")
end)

